package com.nicholas.week1.servlet;

import com.nicholas.week1.annotation.Controller;
import com.nicholas.week1.annotation.Inject;
import com.nicholas.week1.annotation.URL;
import com.nicholas.week1.model.MethodInfo;
import com.nicholas.week1.util.PropertyUtil;
import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Logger;

public class HttpDispatcher extends HttpServlet {

    private Logger logger = Logger.getLogger("HttpDispatcher");
    private Map<String, MethodInfo> handleMethodMapping = new HashMap<>();
    private Map<String,Class<?>> controllerMapping = new HashMap<>();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String requestURI = req.getRequestURI();
        System.out.println(requestURI);
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        String requestMappingPath = StringUtils.substringAfter(requestURI, contextPath);
        System.out.println(requestMappingPath);
        Class<?> clazz = controllerMapping.get(requestMappingPath);

        if (null != clazz) {
            MethodInfo methodInfo = handleMethodMapping.get(requestMappingPath);
            try {
                if (null != methodInfo) {
                    String method = req.getMethod();
                    System.out.println("method="+method);
                    if (!methodInfo.getHttpMethods().contains(method)) {
                        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
                        return;
                    }
                    Method clazzMethod = clazz.getDeclaredMethod(methodInfo.getMethod().getName(), HttpServletRequest.class, HttpServletResponse.class);
                    Object instance = clazz.getDeclaredConstructor().newInstance();
                    inject(clazz,instance);
                    String path = (String) clazzMethod.invoke(instance,req,resp);
                    System.out.println(">>>>>>>>>>>>>>>>>>>"+path);
                    ServletContext servletContext = req.getServletContext();
                    if (!path.startsWith("/")) {
                        path = "/" + path;
                    }
                    System.out.println(path);
                    RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(path);
                    requestDispatcher.forward(req,resp);
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            initMethods();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private void initMethods() throws IllegalAccessException, InstantiationException {
        String basePackage = PropertyUtil.getProperty("base-package");
        Reflections reflections = new Reflections(basePackage);
        Set<Class<?>> controllers = reflections.getTypesAnnotatedWith(Controller.class);
        for (Class<?> clazz : controllers) {
            URL controllerUrl = clazz.getAnnotation(URL.class);
            String requestPath = controllerUrl.value();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(URL.class)) {
                    System.out.println(method.getName());
                    Set<String> supportHttpMethod = findSupportHttpMethod(method);
                    URL url = method.getAnnotation(URL.class);
                    String mainURL = requestPath + url.value();
                    System.out.println("init-path" + mainURL);
                    handleMethodMapping.put(mainURL,new MethodInfo(mainURL,method,supportHttpMethod));
                    controllerMapping.put(mainURL,clazz);
                }
            }

        }

    }

    private void inject(Class<?> clazz, Object instance) {
        try {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Inject.class)) {
                    field.setAccessible(true);
                    Class<?> fieldType = field.getType();
                    String packageName = fieldType.getPackageName();
                    Reflections reflections = new Reflections(packageName);
                    Set<Class<?>> subTypesOf = reflections.getSubTypesOf((Class<Object>) fieldType);
                    for (Class<?> subClazz : subTypesOf) {
                        field.set(instance,subClazz.newInstance());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private Set<String> findSupportHttpMethod(Method method) {
        Set<String> supportedHttpMethods = new LinkedHashSet<>();
        URL url = method.getAnnotation(URL.class);
        if (url != null) {
            supportedHttpMethods.add(url.method());
        } else {
            supportedHttpMethods.addAll(Arrays.asList(HttpMethod.GET, HttpMethod.POST,
                    HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
        }
        return supportedHttpMethods;
    }
}
